
#include "session.h"
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#include <arch_irq.h>

#include <asm/i2s.h>
#endif
#include <asm/delay.h>

#include <uapi/stdlib.h>
#include <uapi/string.h>

#include <math.h>

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
#if !defined(CONFIG_HAVE_DATBUFS) || defined(CONFIG_NO_DATBUFS)
#error configuration CONFIG_HAVE_DATBUFS must be defined, configuration CONFIG_NO_DATBUFS must be deleted !
#endif
#endif

///////////////////////////////////////////////////////////////////////////////////////////////
typedef struct
{
	struct SESSION *session;
	int bufirq[2];
	psysbuf_t recbuf[2];
	audio_info_t info;
	session_speed_t speed;
}session_unit_t;

static inline
int session_buffer_process(session_unit_t *punit, int inirq)
{
	int valid;
	uint32_t count;
	psysbuf_t load_buf;
	struct SESSION *session;
	if (punit == NULL)
		return -EACCES;
	session = punit->session;
	if (session == NULL)
		return -EFAULT;

	if (punit->recbuf[1] != NULL) {
		return -EBUSY;
	}

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	valid = i2s_in_buffer_valid(inirq);
	if (valid <= 0)
		return -EINVAL;
#endif

	load_buf = sysbuf_alloc(SYSBUF_GROUP_DATBUFS);
	if (load_buf) {

		load_buf->offset = 0;
		if (punit->info.samplingsize == 24) {
			load_buf->size = (load_buf->maxsize - 32) / 6;
			count = load_buf->size;
			load_buf->size *= 6;
		}
		else {
			load_buf->size = (load_buf->maxsize - 32) / 4;
			count = load_buf->size;
			load_buf->size *= 4;
		}

		if (count < 1) {
			sysbuf_free(load_buf);
			load_buf = NULL;
		}
		else if (punit->recbuf[0] == NULL) { // may be first
			punit->recbuf[0] = load_buf;
			punit->bufirq[0] = 0;
		}
		else {
			punit->recbuf[1] = load_buf;
			punit->bufirq[1] = 0;
		}
	}

	if (load_buf) {
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		i2s_in_addbuffer(load_buf->haddr + load_buf->offset, count);
#endif
	}

	if (punit->recbuf[1] == NULL)
		session->state = SSTATE_RUNNING_IDLE;
	else
		session->state = SSTATE_RUNNING;

}

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
static void i2s_in_irq_handler(session_unit_t *punit)
{
	sysbuf_t last_buf;

	psysbuf_t load_buf = NULL;
	struct SESSION *session = punit->session;

	if (i2s_in_in_irq())
	{

		i2s_in_lastbuffer(&last_buf);

		if (last_buf.haddr > 0 && last_buf.size > 0)
		{
			if (load_buf == NULL)
			{
				load_buf = punit->recbuf[0];
				if (last_buf.haddr == (load_buf->haddr + load_buf->offset))
				{
					if (last_buf.flags & SYS_BUF_FLAG_EOF)
						load_buf->flags |= SYS_BUF_FLAG_EOF;
					load_buf->size = last_buf.size;
					punit->recbuf[0] = punit->recbuf[1];
					punit->recbuf[1] = NULL;
					punit->bufirq[0] = punit->bufirq[1];
					punit->bufirq[1] = 0;
				}
				else {
					load_buf = NULL;
				}
			}

			if (load_buf == NULL)
			{
				load_buf = punit->recbuf[1];
				if (last_buf.haddr == (load_buf->haddr + load_buf->offset))
				{
					if (last_buf.flags & SYS_BUF_FLAG_EOF)
						load_buf->flags |= SYS_BUF_FLAG_EOF;
					load_buf->size = last_buf.size;
					punit->recbuf[1] = NULL;
					punit->bufirq[1] = 0;
				}
				else {
					load_buf = NULL;
				}
			}
		}

		if (punit->recbuf[0] != NULL) {
			punit->bufirq[0]++;
			if (punit->bufirq[0] >= 4) {
				sysbuf_free(punit->recbuf[0]);
				punit->recbuf[0] = NULL;
				punit->bufirq[0] = 0;
			}
		}

		if (punit->recbuf[1] != NULL) {
			punit->bufirq[1]++;
			if (punit->bufirq[1] >= 4) {
				sysbuf_free(punit->recbuf[1]);
				punit->recbuf[1] = NULL;
				punit->bufirq[1] = 0;
			}
		}

		if (load_buf) {
			SessionBufferPush(session, load_buf);
		}

		if (session->state > SSTATE_INITED
			&& session->state != SSTATE_RUNNING_PAUSE)
		{
			session_buffer_process(punit, 1);
		}

		i2s_in_irq_clear();
	}

	arch_irq_clear(IRQ_I2S_REC);
}
#endif
static int i2s_in_noirq_running(session_unit_t *punit)
{
	struct SESSION *session;
	if (punit == NULL)
		return -EACCES;
	session = punit->session;
	if (session == NULL)
		return -EFAULT;

	if (session->state == SSTATE_RUNNING_IDLE
		|| session->state == SSTATE_STOP)
	{
		uint32_t flags;
		local_irq_save(flags);

		session_buffer_process(punit, 0);

		local_irq_restore(flags);
	}

}

static
SESSIONSTATE SESSIONAPI(SessionRun)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return SSTATE_NULL;

	i2s_in_noirq_running(punit);

	return session->state;
}

static
int SESSIONAPI(SSCMD_STREAM_START)(struct SESSION *session, session_file_t *file)
{
	uint32_t flags;
	session_unit_t *punit = NULL;

	debug("Session '%s' command 'SSCMD_STREAM_START'\n", session->name);

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	if (session->state < SSTATE_INITED)
		return -EACCES;

	session->state = SSTATE_INITED;

	// reset buffer
	SessionBufferDeInit(session);

	if (punit->recbuf[0])
		sysbuf_free(punit->recbuf[0]);
	punit->recbuf[0] = NULL;
	punit->bufirq[0] = 0;
	if (punit->recbuf[1])
		sysbuf_free(punit->recbuf[1]);
	punit->recbuf[1] = NULL;
	punit->bufirq[1] = 0;

#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// hardware initial
	local_irq_save(flags);

	i2s_in_set_samplingrate(punit->info.samplingrate);
	i2s_in_enable_24bit(punit->info.samplingsize == 24 ? 1 : 0);

	local_irq_restore(flags);

	// enable interrupts
	i2s_in_irq_clear();
	arch_irq_clear(IRQ_I2S_REC);
	arch_irq_enable(IRQ_I2S_REC);
#endif
	// clear data & mark

	session->state = SSTATE_STOP;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_PAUSE)(struct SESSION *session)
{
	debug("Session '%s' command 'SSCMD_STREAM_PAUSE'\n", session->name);

	if (session->state == SSTATE_RUNNING
		|| session->state == SSTATE_RUNNING_IDLE)
	{
		uint32_t flags;
		session->state = SSTATE_RUNNING_PAUSE;

		while (i2s_in_stopped() == 0)
			flags++;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
		local_irq_save(flags);
		i2s_in_stop_record();
		local_irq_restore(flags);
#endif
	}
	else {
		return -EPERM;
	}

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_RESUME)(struct SESSION *session)
{
	session_unit_t *punit = NULL;

	debug("Session '%s' command 'SSCMD_STREAM_RESUME'\n", session->name);

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	if (session->state == SSTATE_RUNNING_PAUSE)
	{
		if (punit->recbuf[0])
			sysbuf_free(punit->recbuf[0]);
		punit->recbuf[0] = NULL;
		punit->bufirq[0] = 0;
		if (punit->recbuf[1])
			sysbuf_free(punit->recbuf[1]);
		punit->recbuf[1] = NULL;
		punit->bufirq[1] = 0;

		session->state = SSTATE_RUNNING_IDLE;
	}
	else {
		return -EPERM;
	}

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_STOP)(struct SESSION *session)
{
	uint32_t flags;
	session_unit_t *punit = NULL;

	debug("Session '%s' command  'SSCMD_STREAM_STOP'\n", session->name);

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	// set state
	session->state = SSTATE_INITED;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	local_irq_save(flags);

	// stop hardware
	i2s_in_stop_record();

	// enable & reset hardware
	i2s_in_swrst();

	local_irq_restore(flags);

	// stop irq
	arch_irq_disable(IRQ_I2S_REC);
	i2s_in_irq_clear();
	arch_irq_clear(IRQ_I2S_REC);
#endif
	// free & reset buffer
	SessionBufferDeInit(session);

	if (punit->recbuf[0])
		sysbuf_free(punit->recbuf[0]);
	punit->recbuf[0] = NULL;
	punit->bufirq[0] = 0;
	if (punit->recbuf[1])
		sysbuf_free(punit->recbuf[1]);
	punit->recbuf[1] = NULL;
	punit->bufirq[1] = 0;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EINVAL;

	i2s_in_noirq_running(punit);

	return 0;
}

static
int SESSIONAPI(SSCMD_SET_AUDIO_INFO)(struct SESSION *session, audio_info_t *pinfo)
{
	uint32_t flags;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	debug("Session '%s' command 'SSCMD_SET_AUDIO_INFO'\n", session->name);

	punit->info.samplingrate = pinfo->samplingrate;
	punit->info.samplingsize = (pinfo->samplingsize == 24) ? 24 : 16;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	local_irq_save(flags);

	i2s_in_set_samplingrate(punit->info.samplingrate);
	i2s_in_enable_24bit(punit->info.samplingsize == 24 ? 1 : 0);

	local_irq_restore(flags);
#endif
	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_SETSPEED)(struct SESSION *session, session_speed_t *ps)
{
	uint32_t flags;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	debug("Session '%s' command 'SSCMD_STREAM_SETSPEED' x%d\n", session->name, *ps);

	punit->speed = *ps;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_GETSPEED)(struct SESSION *session, session_speed_t *ps)
{
	session_unit_t *punit = NULL;

	debug("Session '%s' command 'SSCMD_STREAM_GETSPEED'\n", session->name);

	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	*ps = punit->speed;

	return 0;
}

static
int SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(struct SESSION *session, uint32_t *psz)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit == NULL)
		return -EACCES;

	return 0;
}

static
int SESSIONAPI(SessionCommand)(struct SESSION *session, int cmd, void *params)
{
	int ret = 0;
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (session == NULL || punit == NULL)
		return -EINVAL;

	switch (cmd)
	{
	case SSCMD_STREAM_START:
	{
		return SESSIONAPI(SSCMD_STREAM_START)(session, (session_file_t *)params);
	}break;
	case SSCMD_STREAM_PAUSE:
	{
		return SESSIONAPI(SSCMD_STREAM_PAUSE)(session);
	}break;
	case SSCMD_STREAM_RESUME:
	{
		return SESSIONAPI(SSCMD_STREAM_RESUME)(session);
	}break;
	case SSCMD_STREAM_STOP:
	{
		return SESSIONAPI(SSCMD_STREAM_STOP)(session);
	}break;
	case SSCMD_STREAM_BUFCHANGED:
	{
		return SESSIONAPI(SSCMD_STREAM_BUFCHANGED)(session);
	}break;
	case SSCMD_SET_AUDIO_INFO:
	{
		return SESSIONAPI(SSCMD_SET_AUDIO_INFO)(session, (audio_info_t*)params);
	}break;
	case SSCMD_STREAM_SETSPEED:
	{
		return SESSIONAPI(SSCMD_STREAM_SETSPEED)(session, (session_speed_t*)params);
	}break;
	case SSCMD_STREAM_GETSPEED:
	{
		return SESSIONAPI(SSCMD_STREAM_GETSPEED)(session, (session_speed_t*)params);
	}break;
	case SSCMD_STREAM_GETVALIDINSIZE:
	{
		return SESSIONAPI(SSCMD_STREAM_GETVALIDINSIZE)(session, (uint32_t *)params);
	}break;
	default:
	{
		debug("Session '%s' command %d unknown !\n", session->name, cmd);
		ret = -EINVAL;
	};
	};

	return ret;
}

static
void SESSIONAPI(SessionDeInit)(struct SESSION *session)
{
	session_unit_t *punit = NULL;
	if (session != NULL)
		punit = (session_unit_t*)session->handle;
	if (punit != NULL)
		free(punit);

	debug("SessionDeInit : %s ..\n", session->name);
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// close hardware
	arch_irq_disable(IRQ_I2S_REC);
	i2s_in_disable();
	arch_irq_clear(IRQ_I2S_REC);
	arch_irq_free(IRQ_I2S_REC);
#endif
	session->handle = NULL;
}

static irq_desc i2s_in_irq;

static
SESSIONHANDLE SESSIONAPI(SessionInit)(struct SESSION *session)
{
	uint32_t flags;
	session_unit_t *punit = (session_unit_t*)malloc(sizeof(session_unit_t) + 64);
	if (punit == NULL)
		return NULL;

	debug("SessionInit : %s ..\n", session->name);

	memset(punit, 0, sizeof(session_unit_t));

	punit->speed = SESSION_SPEED_X1;
	punit->info.samplingsize = 16;
	punit->info.samplingrate = 44100;

	punit->session = session;
	session->SessionRun = &SESSIONAPI(SessionRun);
	session->SessionCommand = &SESSIONAPI(SessionCommand);
	session->SessionDeInit = &SESSIONAPI(SessionDeInit);
	session->state = SSTATE_INITED;
#ifndef CONFIG_MEDIA_EMULATE_ON_PC
	// enable & reset hardware
	local_irq_save(flags);

	i2s_in_swrst();

	local_irq_restore(flags);

	// install interrupts
	arch_irq_disable(IRQ_I2S_REC);
	arch_irq_clear(IRQ_I2S_REC);

	i2s_in_irq.handler = (irq_handler_t)(&i2s_in_irq_handler);
	i2s_in_irq.params = (void*)punit;

	arch_irq_request(IRQ_I2S_REC, &i2s_in_irq);
#endif
	SessionBufferInit(session);

	session->handle = (SESSIONHANDLE)punit;
	return session->handle;
}

struct SESSION gSession_i2sin =
{ "i2sin", (1 << SST_I2SIN),
&SESSIONAPI(SessionInit),
};
